package com.kmxd.ams.app.use;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableSet;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.app.search.command.StatisticsCmd;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.vo.ArchiveTypeVO;
import com.kmxd.ams.client.arrange.IBoxSvc;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IVolumeSvc;
import com.kmxd.ams.client.event.es.EsUpdateDataEvent;
import com.kmxd.ams.client.jy.IStatisticsSvc;
import com.kmxd.ams.client.jy.dto.StatisticsAddDTO;
import com.kmxd.ams.client.jy.vo.StatisticsVO;
import com.kmxd.ams.client.logs.ILogArchiveSvc;
import com.kmxd.ams.client.oss.IOssAttachmentSvc;
import com.kmxd.ams.client.oss.dto.OssAttachmentQueryDTO;
import com.kmxd.ams.client.oss.vo.OssAttachmentListVO;
import com.kmxd.ams.client.system.*;
import com.kmxd.ams.client.system.dto.SysMessagesAddDTO;
import com.kmxd.ams.client.system.vo.SysUserVO;
import com.kmxd.ams.client.use.IUseAppraiseSvc;
import com.kmxd.ams.client.use.IUseArchSvc;
import com.kmxd.ams.client.use.IUseLogSvc;
import com.kmxd.ams.client.use.IUseOrderSvc;
import com.kmxd.ams.client.use.dto.*;
import com.kmxd.ams.client.use.vo.UseArchListVO;
import com.kmxd.ams.client.use.vo.UseOrderCodeVO;
import com.kmxd.ams.client.use.vo.UseOrderListVO;
import com.kmxd.ams.client.use.vo.UseOrderVO;
import com.kmxd.ams.core.common.bo.LoginUser;
import com.kmxd.ams.core.common.constant.ArchControlConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.constant.MessageConst;
import com.kmxd.ams.core.common.enums.*;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.util.ConvertUtil;
import com.kmxd.ams.core.util.DynamicTableNameUtil;
import com.kmxd.ams.core.util.IdUtil;
import com.kmxd.ams.core.util.SecurityUtil;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.entity.Box;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Volume;
import com.kmxd.ams.infra.logs.entity.LogArchive;
import com.kmxd.ams.infra.system.entity.SysDept;
import com.kmxd.ams.infra.system.entity.SysRole;
import com.kmxd.ams.infra.system.entity.SysUser;
import com.kmxd.ams.infra.tmpl.dto.ArchTmplBO;
import com.kmxd.ams.infra.use.entity.UseAppraise;
import com.kmxd.ams.infra.use.entity.UseArch;
import com.kmxd.ams.infra.use.entity.UseOrder;
import com.kmxd.ams.infra.use.mapper.UseOrderMapper;
import java.util.*;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 跨全宗/单位利用主表 服务实现类
 *
 * @author ZuoLG
 * @since 2022-08-02
 */
@Service
@AllArgsConstructor
public class UseOrderSvcImpl extends ServiceImpl<UseOrderMapper, UseOrder> implements IUseOrderSvc {

  private final AmsEventPublisher amsEventPublisher;
  private UseOrderMapper useOrderMapper;
  private IUseArchSvc useArchSvc;
  private IUseLogSvc useLogSvc;
  private ILogArchiveSvc logArchiveSvc;
  private IDocumentSvc documentSvc;
  private IVolumeSvc volumeSvc;
  private IOssAttachmentSvc ossAttachmentSvc;
  private ISysMessagesSvc messagesSvc;
  private ISysDeptSvc sysDeptSvc;
  private ISysUserSvc userSvc;
  private ISysRoleSvc roleSvc;
  private IConfI18nSvc confI18nSvc;
  private final StatisticsCmd statisticsCmd;
  private ISysUserSvc sysUserSvc;
  private IStatisticsSvc statisticsService;

  @Override
  public Page<UseOrderListVO> selectPage(Page reqPage, UseOrderQueryDTO req) {
    LambdaQueryWrapper<UseOrder> queryWrapper =
        QueryGen.init(new QueryWrapper<UseOrder>(), req).lambda();
    queryWrapper.orderByDesc(UseOrder::getCreateTime);
    // 实体利用
    buildQueryType(req, queryWrapper);
    Page<UseOrder> page = useOrderMapper.selectPage(reqPage, queryWrapper);
    Page<UseOrderListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    List<UseOrderListVO> list = BeanUtil.copyToList(page.getRecords(), UseOrderListVO.class);
    List<UseOrder> lateList = Lists.newArrayList();
    list.forEach(
        vo -> {
          // 设置电子文件
          buildVO(vo);
          if (checkBorrowLate(vo.getExpireTime(), vo.getStatus())) {
            vo.setStatus(UseOrderStatusEnum.LATE.getCode());
            lateList.add(BeanUtil.copyProperties(vo, UseOrder.class));
          }
        });
    voPage.setRecords(list);
    if (ObjectUtil.isNotEmpty(lateList)) {
      updateBatchById(lateList);
    }
    return voPage;
  }

  /**
   * 设置查询类型条件
   *
   * @param req
   * @param queryWrapper
   */
  private void buildQueryType(UseOrderQueryDTO req, LambdaQueryWrapper<UseOrder> queryWrapper) {
    if (ObjectUtil.isNotNull(req.getQueryType())) {
      // 1实体利用
      if (ObjectUtil.equals(1, req.getQueryType())) {
        queryWrapper.in(
            UseOrder::getEntType,
            UseOrderTypeEnum.LOAN_IN_KIND.getCode(),
            UseOrderTypeEnum.ON_SITE_BORROWING.getCode(),
            UseOrderTypeEnum.COPY_OUT.getCode());
      }
      // 电子利用
      if (ObjectUtil.equals(2, req.getQueryType())) {
        queryWrapper.in(UseOrder::getEntType, UseOrderTypeEnum.ELECTRONIC_USE.getCode());
      }
    }

    if (ObjectUtil.isNotNull(req.getAuditType())) {
      queryWrapper.isNotNull(UseOrder::getWorkFlowId);
      // 审核通过
      if (ObjectUtil.equals(UseOrderStatusEnum.PASSED.getCode(), req.getAuditType())) {
        queryWrapper.in(
            UseOrder::getStatus,
            UseOrderStatusEnum.PASSED.getCode(),
            UseOrderStatusEnum.FINISHED.getCode(),
            UseOrderStatusEnum.ABNORMAL.getCode(),
            UseOrderStatusEnum.LATE.getCode(),
            UseOrderStatusEnum.WAIT_RECEIVE.getCode());
      }
      // 审批中
      if (ObjectUtil.equals(UseOrderStatusEnum.UNDER_REVIEW.getCode(), req.getAuditType())) {
        queryWrapper.eq(UseOrder::getStatus, UseOrderStatusEnum.UNDER_REVIEW.getCode());
      }
      // 驳回
      if (ObjectUtil.equals(UseOrderStatusEnum.REFUSE.getCode(), req.getAuditType())) {
        queryWrapper.eq(UseOrder::getStatus, UseOrderStatusEnum.REFUSE.getCode());
      }
    }
  }

  @Override
  public Page<UseOrderListVO> selectUserPage(Page reqPage, UseOrderQueryDTO req) {
    if (Objects.isNull(req.getEntType())) {
      throw BizTipException.instance(ErrorCode.VALIDATE_NOT_NULL, "利用类型不能为空");
    }
    LambdaQueryWrapper<UseOrder> queryWrapper =
        QueryGen.init(new QueryWrapper<UseOrder>(), req).lambda();
    queryWrapper.eq(UseOrder::getUserId, SecurityUtil.getUserId());
    queryWrapper.orderByDesc(UseOrder::getCreateTime);
    Page<UseOrder> page = useOrderMapper.selectPage(reqPage, queryWrapper);
    Page<UseOrderListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    List<UseOrderListVO> list = BeanUtil.copyToList(page.getRecords(), UseOrderListVO.class);
    List<UseOrder> lateList = Lists.newArrayList();
    list.forEach(
        vo -> {
          buildVO(vo);
          if (checkBorrowLate(vo.getExpireTime(), vo.getStatus())) {
            vo.setStatus(UseOrderStatusEnum.LATE.getCode());
            lateList.add(BeanUtil.copyProperties(vo, UseOrder.class));
          }
        });
    voPage.setRecords(list);
    if (ObjectUtil.isNotEmpty(lateList)) {
      updateBatchById(lateList);
    }
    return voPage;
  }

  /** 过期借阅检查 */
  private boolean checkBorrowLate(Date endTime, Integer status) {
    // 借阅中的才修改逾期状态
    if (!ImmutableSet.of(UseOrderStatusEnum.PASSED.getCode()).contains(status)) {
      return false;
    }
    if (ObjectUtil.isNull(endTime)) {
      return false;
    }
    DateTime date = DateUtil.date();
    return date.after(endTime);
  }

  private void buildVO(UseOrderListVO vo) {
    // 设置电子文件
    OssAttachmentQueryDTO qry = new OssAttachmentQueryDTO();
    qry.setPid(vo.getId());
    qry.setType(OssAttachmentTypeEnum.USE.getType());

    List<OssAttachmentListVO> ossAttachmentListVOS = ossAttachmentSvc.selectList(qry);
    if (Objects.nonNull(ossAttachmentListVOS)) {
      vo.setFile(ossAttachmentListVOS);
    }
    IUseAppraiseSvc useAppraiseSvc = SpringUtil.getBean(IUseAppraiseSvc.class);
    UseAppraise one =
        useAppraiseSvc.getOne(
            Wrappers.<UseAppraise>lambdaQuery().eq(UseAppraise::getWorkId, vo.getId()));
    vo.setAppraised(ObjectUtil.isNotNull(one));
  }

  @Override
  public List<UseOrderListVO> selectList(UseOrderQueryDTO req) {
    LambdaQueryWrapper<UseOrder> queryWrapper =
        QueryGen.init(new QueryWrapper<UseOrder>(), req).lambda();
    queryWrapper.orderByDesc(UseOrder::getCreateTime);
    buildQueryType(req, queryWrapper);
    List<UseOrder> list = useOrderMapper.selectList(queryWrapper);
    List<UseOrder> lateList = Lists.newArrayList();
    List<UseOrderListVO> res =
        list.stream()
            .map(
                item -> {
                  // 设置电子文件
                  UseOrderListVO vo = BeanUtil.copyProperties(item, UseOrderListVO.class);
                  buildVO(vo);
                  if (checkBorrowLate(vo.getExpireTime(), vo.getStatus())) {
                    vo.setStatus(UseOrderStatusEnum.LATE.getCode());
                    lateList.add(BeanUtil.copyProperties(vo, UseOrder.class));
                  }
                  return vo;
                })
            .collect(Collectors.toList());
    if (ObjectUtil.isNotEmpty(lateList)) {
      updateBatchById(lateList);
    }
    return res;
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public UseOrderVO add(UseOrderAddDTO req) {
    if (ObjectUtil.isEmpty(req.getUseArchList())) {
      throw BizTipException.instance(ErrorCode.VALIDATE_NOT_NULL, "请选择档案进行利用申请");
    }

    List<UseArchAddDTO> reqUseArchList = req.getUseArchList();
    // 兼容前端检索，未找到档案情况
    reqUseArchList.forEach(
        item -> {
          if (ObjectUtil.isNull(item.getArchId()) && ObjectUtil.isNotNull(item.getId())) {
            item.setArchId(item.getId());
          }
        });
    req.setUseArchList(reqUseArchList);

    UseOrder entity = BeanUtil.copyProperties(req, UseOrder.class);
    setRoleDeptInfo(req, entity);
    entity.setArchNum(
        Optional.ofNullable(req.getUseArchList()).orElse(Lists.newArrayList()).size());
    long workId = IdWorker.getId();
    entity.setStatus(UseOrderStatusEnum.UNDER_REVIEW.getCode());

    // 前台申请
    if (BooleanUtil.isTrue(req.getApply())) {
      entity.setWorkFlowId(workId);
      entity.setDeptId(SecurityUtil.getLoginUser().getDeptId());
      entity.setDeptName(SecurityUtil.getLoginUser().getDeptName());
      entity.setComId(SecurityUtil.getCropId());
      entity.setUserName(SecurityUtil.getRealname());
      entity.setFondsId(SecurityUtil.getFondsId());
      entity.setUserId(SecurityUtil.getUserId());
      entity.setStatus(UseOrderStatusEnum.UNDER_REVIEW.getCode());
    } else {
      entity.setStatus(UseOrderStatusEnum.WAIT_RECEIVE.getCode());
    }
    entity.setId(workId);
    entity.setOnlineBrowser(Boolean.TRUE);
    useOrderMapper.insert(entity);
    if (ObjectUtil.isEmpty(req.getUseArchList())) {
      return BeanUtil.copyProperties(entity, UseOrderVO.class);
    }
    // 保存档案利用信息
    List<UseArch> useArchList =
        req.getUseArchList().stream()
            .map(
                arch -> {
                  UseArch useArch = ConvertUtil.toBean(arch, UseArch.class);
                  Integer status =
                      BooleanUtil.isTrue(req.getApply())
                          ? UseOrderStatusEnum.UNDER_REVIEW.getCode()
                          : UseOrderStatusEnum.WAIT_RECEIVE.getCode();
                  useArch.setStatus(status);
                  useArch.setUseSequence(1);
                  if (ObjectUtil.contains(useArch.getTableId(), ArchTmplBO.DOCUMENT)) {
                    setDocArchInfo(entity, arch, useArch);
                  }
                  if (ObjectUtil.contains(useArch.getTableId(), ArchTmplBO.VOLUME)) {
                    setVolArchInfo(entity, arch, useArch);
                  }

                  if (ObjectUtil.contains(useArch.getTableId(), ArchTmplBO.BOX)) {
                    setVolArchInfo(entity, arch, useArch);
                  }
                  if (ObjectUtil.isNull(useArch.getApplyArchNum())) {
                    useArch.setApplyArchNum(1);
                  }
                  useArch.setComId(arch.getComId());
                  useArch.setFondsId(arch.getFondsId());
                  return useArch;
                })
            .filter(ObjectUtil::isNotNull)
            .collect(Collectors.toList());

    // 电子利用，实体利用，检查以卷装订
    if (ImmutableSet.of(
            UseOrderTypeEnum.ELECTRONIC_USE.getCode(),
            UseOrderTypeEnum.LOAN_IN_KIND.getCode(),
            UseOrderTypeEnum.ON_SITE_BORROWING.getCode(),
            UseOrderTypeEnum.COPY_OUT.getCode())
        .contains(req.getEntType())) {
      Map<ImmutablePair<Long, String>, List<UseArch>> group =
          useArchList.stream()
              .filter(item -> item.getTableId().contains(ArchTmplBO.DOCUMENT))
              .collect(
                  Collectors.groupingBy(
                      item -> ImmutablePair.of(item.getArchTypeId(), item.getTableId())));
      group.forEach(
          (pair, list) -> {
            Document qryDoc = new Document();
            String docIds =
                list.stream()
                    .map(item -> String.valueOf(item.getArchId()))
                    .collect(Collectors.joining(","));
            qryDoc.setDocIds(docIds);
            qryDoc.setArchTypeId(pair.getLeft());
            qryDoc.setTableId(pair.getRight());
            IArchiveTypeSvc archiveTypeSvc = SpringUtil.getBean(IArchiveTypeSvc.class);
            ArchiveTypeVO archiveTypeVO = archiveTypeSvc.view(pair.getLeft());
            List<Document> documents = documentSvc.selectListWithNoDataScope(qryDoc);
            for (Document doc : documents) {
              checkBindWithVol(archiveTypeVO, doc);
            }
          });
    }

    useArchSvc.saveBatch(useArchList);
    // 出入库登记
    if (ImmutableSet.of(
            UseOrderTypeEnum.ON_SITE_BORROWING.getCode(), UseOrderTypeEnum.LOAN_IN_KIND.getCode())
        .contains(req.getEntType())) {
      entity.setStatus(UseOrderStatusEnum.WAIT_RECEIVE.getCode());
      // 预占库存
      checkOutArchStorage(useArchList, false);
    }

    return BeanUtil.copyProperties(entity, UseOrderVO.class);
  }

  /**
   * 检测出库档案库存，并进行预占处理
   *
   * @param detailList
   */
  @Transactional(rollbackFor = Exception.class)
  public void checkOutArchStorage(List<UseArch> detailList, boolean inArch) {
    Map<ImmutablePair<Long, String>, List<UseArch>> group =
        detailList.stream()
            .collect(
                Collectors.groupingBy(
                    item -> ImmutablePair.of(item.getArchTypeId(), item.getTableId())));
    group.forEach(
        (pair, list) -> {
          Long archTypeId = pair.getLeft();
          IArchiveTypeSvc archiveTypeSvc = SpringUtil.getBean(IArchiveTypeSvc.class);
          ArchiveTypeVO archiveTypeVO = archiveTypeSvc.view(archTypeId);
          String tableId = pair.getRight();
          // 盒入库
          if (tableId.contains(ArchTmplBO.BOX)) {
            Box qryBox = new Box();
            qryBox.setTableId(archiveTypeVO.getBoxTableId());
            qryBox.setArchTypeId(archTypeId);
            qryBox.setStatus(ArchStatusEnum.S1.getCode());
            String boxIds =
                list.stream()
                    .map(item -> String.valueOf(item.getArchId()))
                    .collect(Collectors.joining(","));
            qryBox.setBoxIds(boxIds);
            DynamicTableNameUtil.setTableName(archiveTypeVO.getBoxTableId());
            IBoxSvc boxSvc = SpringUtil.getBean(IBoxSvc.class);
            List<Box> boxes = boxSvc.selectListWithNoDataScope(qryBox);
            Map<Long, Integer> archMap =
                list.stream()
                    .collect(Collectors.toMap(UseArch::getArchId, UseArch::getApplyArchNum));
            List<Document> documentList = Lists.newArrayList();
            // 盒库存处理
            for (Box box : boxes) {
              int regNum = archMap.get(box.getId());
              Integer currentBoxStorageNum = Optional.ofNullable(box.getStorageNum()).orElse(0);
              int newBoxStorageNum =
                  inArch
                      ? Math.addExact(currentBoxStorageNum, regNum)
                      : Math.subtractExact(currentBoxStorageNum, regNum);
              // 出库，查看数量是否小于0
              if (!inArch) {
                if (newBoxStorageNum < 0) {
                  throw BizTipException.instance(
                      ErrorCode.ARCHIVE_STOCK_NOT_ENOUGH, "盒：" + box.getBoxNo() + " 库存不足");
                }
              }

              box.setStorageNum(newBoxStorageNum);
              List<Document> documents =
                  checkDocument(
                      archTypeId,
                      archiveTypeVO,
                      archiveTypeVO.getBoxTableId(),
                      box.getId(),
                      inArch,
                      regNum);
              if (ObjectUtil.isNotEmpty(documents)) {
                documentList.addAll(documents);
              }
            }
            DynamicTableNameUtil.setTableName(archiveTypeVO.getBoxTableId());
            boxSvc.updateBatchById(boxes);
            DynamicTableNameUtil.setTableName(archiveTypeVO.getDocTableId());
            documentSvc.updateBatchById(documentList);
            // 更新es数据
            List<Long> newVolIds = boxes.stream().map(Box::getId).collect(Collectors.toList());
            amsEventPublisher.publishEvent(
                new EsUpdateDataEvent(
                    newVolIds, archiveTypeVO.getId(), archiveTypeVO.getBoxTableId()));
            List<Long> newDocIds =
                documentList.stream().map(Document::getId).collect(Collectors.toList());
            amsEventPublisher.publishEvent(
                new EsUpdateDataEvent(
                    newDocIds, archiveTypeVO.getId(), archiveTypeVO.getDocTableId()));
          }

          // 案卷入库
          if (tableId.contains(ArchTmplBO.VOLUME)) {
            Volume qryVol = new Volume();
            qryVol.setTableId(archiveTypeVO.getVolTableId());
            qryVol.setArchTypeId(archTypeId);
            qryVol.setStatus(ArchStatusEnum.S1.getCode());
            String volIds =
                list.stream()
                    .map(item -> String.valueOf(item.getArchId()))
                    .collect(Collectors.joining(","));
            qryVol.setVolIds(volIds);
            DynamicTableNameUtil.setTableName(archiveTypeVO.getVolTableId());
            List<Volume> volumes = volumeSvc.selectListWithNoDataScope(qryVol);
            if (ObjectUtil.isEmpty(volumes)) {
              throw BizTipException.instance(ErrorCode.USE_ARCH_DATA_NOT_FOUND, "未找到案卷信息");
            }
            Map<Long, Integer> archMap =
                list.stream()
                    .collect(Collectors.toMap(UseArch::getArchId, UseArch::getApplyArchNum));
            List<Document> documentList = Lists.newArrayList();
            for (Volume vol : volumes) {
              int regNum = archMap.get(vol.getId());
              Integer currentBoxStorageNum = Optional.ofNullable(vol.getStorageNum()).orElse(0);
              int newBoxStorageNum =
                  inArch
                      ? Math.addExact(currentBoxStorageNum, regNum)
                      : Math.subtractExact(currentBoxStorageNum, regNum);
              if (!inArch) {
                if (newBoxStorageNum < 0) {
                  throw BizTipException.instance(
                      ErrorCode.ARCHIVE_STOCK_NOT_ENOUGH, "案卷: 【" + vol.getTitle() + "】 库存不足");
                }
              }

              vol.setStorageNum(newBoxStorageNum);
              List<Document> documents =
                  checkDocument(
                      archTypeId,
                      archiveTypeVO,
                      archiveTypeVO.getVolTableId(),
                      vol.getId(),
                      inArch,
                      regNum);
              if (ObjectUtil.isNotEmpty(documents)) {
                documentList.addAll(documents);
              }
            }
            DynamicTableNameUtil.setTableName(archiveTypeVO.getVolTableId());
            volumeSvc.updateBatchById(volumes);
            DynamicTableNameUtil.setTableName(archiveTypeVO.getDocTableId());
            documentSvc.updateBatchById(documentList);
            List<Long> newVolIds = volumes.stream().map(Volume::getId).collect(Collectors.toList());
            // 更新es数据
            amsEventPublisher.publishEvent(
                new EsUpdateDataEvent(
                    newVolIds, archiveTypeVO.getId(), archiveTypeVO.getVolTableId()));
            List<Long> newDocIds =
                documentList.stream().map(Document::getId).collect(Collectors.toList());
            amsEventPublisher.publishEvent(
                new EsUpdateDataEvent(
                    newDocIds, archiveTypeVO.getId(), archiveTypeVO.getDocTableId()));
          }
          // 文书入库
          if (tableId.contains(ArchTmplBO.DOCUMENT)) {
            String docIds =
                list.stream()
                    .map(item -> String.valueOf(item.getArchId()))
                    .collect(Collectors.joining(","));
            Document qryDoc = new Document();
            qryDoc.setDocIds(docIds);
            qryDoc.setArchTypeId(archTypeId);
            qryDoc.setTableId(archiveTypeVO.getDocTableId());
            qryDoc.setStatus(ArchStatusEnum.S1.getCode());
            DynamicTableNameUtil.setTableName(archiveTypeVO.getDocTableId());
            List<Document> documents = documentSvc.selectListWithNoDataScope(qryDoc);
            Map<Long, Integer> archMap =
                list.stream()
                    .collect(Collectors.toMap(UseArch::getArchId, UseArch::getApplyArchNum));
            for (Document doc : documents) {
              int regNum = archMap.get(doc.getId());
              // 校验装订方式： 以卷装订的 卷内文件不能独立入库
              checkBindWithVol(archiveTypeVO, doc);

              Integer currentStorageNum = Optional.ofNullable(doc.getStorageNum()).orElse(0);
              int newBoxStorageNum =
                  inArch
                      ? Math.addExact(currentStorageNum, regNum)
                      : Math.subtractExact(currentStorageNum, regNum);
              // 出库操作，库存不足提示
              if (!inArch) {
                if (newBoxStorageNum < 0) {
                  throw BizTipException.instance(
                      ErrorCode.ARCHIVE_STOCK_NOT_ENOUGH, "文件:" + doc.getTitle() + " 库存不足");
                }
              }
              doc.setStorageNum(newBoxStorageNum);
            }
            DynamicTableNameUtil.setTableName(archiveTypeVO.getDocTableId());
            documentSvc.updateBatchById(documents);
            // 更新es数据
            List<Long> newDocIds =
                documents.stream().map(Document::getId).collect(Collectors.toList());
            amsEventPublisher.publishEvent(
                new EsUpdateDataEvent(
                    newDocIds, archiveTypeVO.getId(), archiveTypeVO.getDocTableId()));
          }
        });
  }

  /**
   * 卷内 盒内文件出入库
   *
   * @param archTypeId
   * @param archiveTypeVO
   * @param parentTableId
   * @param parentId
   * @param inArch
   * @param regNum
   * @return
   */
  private List<Document> checkDocument(
      Long archTypeId,
      ArchiveTypeVO archiveTypeVO,
      String parentTableId,
      Long parentId,
      boolean inArch,
      int regNum) {
    // 盒内文件
    Document qryDoc = new Document();
    qryDoc.setArchTypeId(archTypeId);
    qryDoc.setTableId(archiveTypeVO.getDocTableId());
    qryDoc.setStatus(ArchStatusEnum.S1.getCode());
    if (CharSequenceUtil.isNotBlank(parentTableId)) {
      if (parentTableId.contains(ArchTmplBO.BOX)) {
        qryDoc.setBoxId(parentId);
      }
      if (parentTableId.contains(ArchTmplBO.VOLUME)) {
        qryDoc.setVolId(parentId);
      }
    }
    DynamicTableNameUtil.setTableName(archiveTypeVO.getDocTableId());
    List<Document> documents = documentSvc.selectListWithNoDataScope(qryDoc);
    for (Document doc : documents) {
      Integer currentStorageNum = Optional.ofNullable(doc.getStorageNum()).orElse(0);
      int newStorageNum =
          inArch
              ? Math.addExact(currentStorageNum, regNum)
              : Math.subtractExact(currentStorageNum, regNum);
      if (!inArch) {
        if (newStorageNum < 0) {
          throw BizTipException.instance(
              ErrorCode.ARCHIVE_STOCK_NOT_ENOUGH, doc.getTitle() + " 库存不足");
        }
      }
      doc.setStorageNum(newStorageNum);
    }
    return documents;
  }

  private void setVolArchInfo(UseOrder entity, UseArchAddDTO arch, UseArch useArch) {
    Volume qry = new Volume();
    qry.setTableId(arch.getTableId());
    Long archId = ObjectUtil.isNull(arch.getArchId()) ? arch.getId() : arch.getArchId();
    qry.setId(archId);
    Volume doc = volumeSvc.view(qry);
    if (ObjectUtil.isNull(doc)) {
      return;
    }
    useArch.setArchId(doc.getId());
    useArch.setTitle(doc.getTitle());
    useArch.setArchCode(doc.getArchCode());
    useArch.setFillingDepartment(doc.getFillingDepartment());
    useArch.setYear(doc.getYear());
    useArch.setSecurity(doc.getSecurity());
    useArch.setRetentionPeriod(doc.getRetentionPeriod());
    useArch.setFileType(doc.getFileType());
    useArch.setFillingNum(doc.getFillingNum());
    useArch.setOrderId(entity.getId());
    useArch.setDeptId(doc.getDepartmentId());
    useArch.setDeptName(doc.getDepartment());
    useArch.setUserId(entity.getUserId());
    useArch.setId(null);
  }

  private void setDocArchInfo(UseOrder entity, UseArchAddDTO arch, UseArch useArch) {
    Document qry = new Document();
    qry.setTableId(arch.getTableId());
    Long archId = ObjectUtil.isNull(arch.getArchId()) ? arch.getId() : arch.getArchId();
    qry.setId(archId);
    Document doc = documentSvc.view(qry);
    if (ObjectUtil.isNull(doc)) {
      return;
    }
    useArch.setArchId(doc.getId());
    useArch.setTitle(doc.getTitle());
    useArch.setArchCode(doc.getArchCode());
    useArch.setFillingDepartment(doc.getFillingDepartment());
    useArch.setYear(doc.getYear());
    useArch.setSecurity(doc.getSecurity());
    useArch.setRetentionPeriod(doc.getRetentionPeriod());
    useArch.setFileType(doc.getFileType());
    useArch.setDocNumber(doc.getDocNumber());
    useArch.setFillingNum(doc.getFillingNum());
    useArch.setOrderId(entity.getId());
    useArch.setDeptId(doc.getDepartmentId());
    useArch.setDeptName(doc.getDepartment());
    useArch.setUserId(entity.getUserId());
    useArch.setId(null);
  }

  private void setRoleDeptInfo(UseOrderAddDTO req, UseOrder entity) {
    if (Objects.nonNull(req.getDeptId())) {

      SysDept dept = sysDeptSvc.getById(req.getDeptId());
      entity.setDeptName(dept.getName());
      entity.setUserName(dept.getName() + "/");
    }
    if (Objects.nonNull(req.getRoleId())) {
      SysRole role = roleSvc.getById(req.getRoleId());
      String name = Optional.ofNullable(entity.getUserName()).orElse("");
      entity.setUserName(name + role.getRoleName() + "/");
    }

    if (Objects.nonNull(req.getUserId())) {
      SysUser user = userSvc.getById(req.getUserId());
      String name = Optional.ofNullable(entity.getUserName()).orElse("") + user.getRealname();
      entity.setUserName(name);
    }
    if (ObjectUtil.equals(req.getUserType(), UseUserTypeEnum.OUTSIDE_THE_SYSTEM.getType())) {
      entity.setUserName(StrUtil.format("{}/{}", req.getUserUnit(), req.getUserInfo()));
    }
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(UseOrderEditDTO req) {
    UseOrder entity = BeanUtil.copyProperties(req, UseOrder.class);
    useOrderMapper.updateById(entity);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(String ids) {
    Set<Long> idList = IdUtil.split(ids);
    this.baseMapper.deleteBatchIds(idList);
  }

  @Override
  public UseOrderVO view(Long id) {
    UseOrder entity = useOrderMapper.selectById(id);
    UseOrderVO useOrderVO = BeanUtil.copyProperties(entity, UseOrderVO.class);
    UseOrderListVO listVO = BeanUtil.copyProperties(useOrderVO, UseOrderListVO.class);
    buildVO(listVO);
    UseOrderVO vo = BeanUtil.copyProperties(listVO, UseOrderVO.class);
    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(entity.getId());
    List<UseArchListVO> archList = useArchSvc.selectList(qry);
    useOrderVO.setArchList(archList);
    vo.setArchList(archList);
    // 设置电子文件
    OssAttachmentQueryDTO qryOss = new OssAttachmentQueryDTO();
    qryOss.setPid(vo.getId());
    qryOss.setType(OssAttachmentTypeEnum.USE.getType());

    List<OssAttachmentListVO> ossAttachmentListVOS = ossAttachmentSvc.selectList(qryOss);
    if (Objects.nonNull(ossAttachmentListVOS)) {
      vo.setFile(ossAttachmentListVOS);
    }
    return vo;
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void operate(UseOrderManageDTO req) {
    switch (req.getType()) {
      case RECEIVE:
        receive(req);
        return;
      case RENEW:
        renew(req);
        return;
      case RETURN:
        returnArch(req);
        return;
      case STOP_USE:
        stopUse(req);
        return;
      case LOST:
        lost(req);
        return;
      case NOTICE_TO_RECEIVE:
        noticeToReceive(req);
        return;
      case RECALL:
        recall(req);
        return;
      default:
        throw BizTipException.instance(ErrorCode.EXCEPTION, "未知操作类型");
    }
  }

  @Transactional(rollbackFor = Exception.class)
  public void receive(UseOrderManageDTO req) {

    UseOrderVO useOrderVO = checkUseOrder(req);

    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(arch -> !arch.getStatus().equals(UseOrderStatusEnum.WAIT_RECEIVE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.isNotEmpty(filterList)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "利用档案类型不为待领取");
    }
    Integer status = UseOrderStatusEnum.PASSED.getCode();
    // 复制外发，领取后就结束
    if (UseOrderTypeEnum.COPY_OUT.getCode().equals(useOrderVO.getEntType())) {
      // 归还预占库存
      status = UseOrderStatusEnum.FINISHED.getCode();
    }
    String period = useOrderVO.getPeriod();
    DateTime offset = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, Integer.parseInt(period));
    Integer finalStatus = status;
    useArches.forEach(
        arch -> {
          arch.setStartTime(new Date());
          arch.setEndTime(offset);
          arch.setStatus(finalStatus);
          arch.setUseCode(req.getCode());
          if (req.getCode().contains(req.getSequence().toString())) {
            arch.setUseSequence(req.getSequence());
          }
        });
    useArchSvc.updateBatchById(useArches);
    // 添加日志
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    req.setArchContent(archContent);
    req.setCreateBy(SecurityUtil.getUserId());
    req.setCreateUserName(SecurityUtil.getUsername());
    useLogSvc.asyncSaveLog(req);
    useOrderVO.setOpComId(SecurityUtil.getCropId());
    useOrderVO.setOpFondsId(SecurityUtil.getFondsId());
    useOrderVO.setOpUserId(SecurityUtil.getUserId());
    useOrderVO.setOpUsername(SecurityUtil.getLoginUser().getRealName());
    useOrderVO.setOpDeptId(SecurityUtil.getLoginUser().getDeptId());
    useOrderVO.setOpTime(new Date());
    useOrderVO.setExpireTime(offset);
    useOrderVO.setStatus(status);
    UseOrder entity = BeanUtil.copyProperties(useOrderVO, UseOrder.class);
    useOrderMapper.updateById(entity);
  }

  private void saveLogArch(Document doc, UseOrderOperateEnum type) {
    LogArchive useLog = ConvertUtil.toBean(doc, LogArchive.class);
    useLog.setId(null);
    useLog.setType(ArchManageEnum.USE.getType());
    if (type.equals(UseOrderOperateEnum.RECEIVE)) {
      useLog.setActionDescription(confI18nSvc.format(MessageConst.REGISTER_TO_BORROW));
    } else {
      useLog.setActionDescription(confI18nSvc.format(MessageConst.REGISTER_TO_RETURN));
    }
    setUserInfo(useLog);
    logArchiveSvc.save(useLog);
    LogArchive outOfStockLog = ConvertUtil.toBean(doc, LogArchive.class);
    outOfStockLog.setId(null);
    outOfStockLog.setType(ArchManageEnum.SAVE.getType());
    if (type.equals(UseOrderOperateEnum.RECEIVE)) {
      outOfStockLog.setActionDescription(confI18nSvc.format(MessageConst.OUT_OF_STOCK_ONE));
    } else {
      outOfStockLog.setActionDescription(confI18nSvc.format(MessageConst.RETURN_OF_STOCK_ONE));
    }
    setUserInfo(outOfStockLog);
    logArchiveSvc.save(outOfStockLog);
  }

  private void setUserInfo(LogArchive log) {
    log.setComId(SecurityUtil.getCropId());
    log.setFondsId(SecurityUtil.getFondsId());
    log.setUserId(SecurityUtil.getUserId());
    LoginUser loginUser = SecurityUtil.getLoginUser();
    log.setDeptId(Objects.requireNonNull(loginUser).getDeptId());
    log.setDeptName(loginUser.getDeptName());
    log.setRoleId(loginUser.getRoleId());
    log.setUserName(loginUser.getUsername());
    log.setUserCode(loginUser.getSysUser().getAccount());
    log.setOpTime(new Date());
    log.setIp(loginUser.getIpAddress());
  }

  /**
   * 续借
   *
   * @param req
   */
  @Transactional(rollbackFor = Exception.class)
  public void renew(UseOrderManageDTO req) {

    checkUseOrder(req);
    UseOrder view = useOrderMapper.selectById(req.getId());
    view.setStatus(UseOrderStatusEnum.PASSED.getCode());
    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(
                arch ->
                    arch.getStatus().equals(UseOrderStatusEnum.PASSED.getCode())
                        || arch.getStatus().equals(UseOrderStatusEnum.LATE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "逾期，已领取的文档才能进行续借操作");
    }
    view.setExpireTime(req.getUseDate());
    useOrderMapper.updateById(view);
    useArches.forEach(
        arch -> {
          arch.setEndTime(req.getUseDate());
          arch.setStatus(UseOrderStatusEnum.PASSED.getCode());
        });
    useArchSvc.updateBatchById(useArches);
    // 添加日志
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    req.setArchContent(archContent);
    req.setCreateBy(SecurityUtil.getUserId());
    req.setCreateUserName(SecurityUtil.getUsername());
    useLogSvc.asyncSaveLog(req);
  }

  private UseOrderVO checkUseOrder(UseOrderManageDTO req) {
    return Optional.ofNullable(view(req.getId()))
        .orElseThrow(() -> BizTipException.instance(ErrorCode.NOT_FOUND, "未找到利用信息"));
  }

  /**
   * 归还
   *
   * @param req
   */
  @Transactional(rollbackFor = Exception.class)
  public void returnArch(UseOrderManageDTO req) {

    UseOrder useOrder = useOrderMapper.selectById(req.getId());
    useOrder.setStatus(UseOrderStatusEnum.FINISHED.getCode());
    useOrderMapper.updateById(useOrder);
    checkUseOrder(req);

    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(
                arch ->
                    arch.getStatus().equals(UseOrderStatusEnum.LATE.getCode())
                        || arch.getStatus().equals(UseOrderStatusEnum.PASSED.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "逾期，已领取的文档才能进行归还操作");
    }
    useArches.forEach(
        arch -> {
          arch.setStatus(UseOrderStatusEnum.FINISHED.getCode());
          arch.setUpdateBy(SecurityUtil.getUserId());
          arch.setUpdateTime(new Date());
        });
    useArchSvc.updateBatchById(useArches);
    if (ImmutableSet.of(
            UseOrderTypeEnum.LOAN_IN_KIND.getCode(), UseOrderTypeEnum.ON_SITE_BORROWING.getCode())
        .contains(useOrder.getEntType())) {
      // 归还库存
      checkOutArchStorage(useArches, true);
    }
    // 添加日志
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    req.setArchContent(archContent);
    req.setCreateBy(SecurityUtil.getUserId());
    req.setCreateUserName(SecurityUtil.getUsername());
    useLogSvc.asyncSaveLog(req);
  }

  /**
   * 终止利用
   *
   * @param req
   */
  public void stopUse(UseOrderManageDTO req) {
    UseOrder useOrder = useOrderMapper.selectById(req.getId());
    useOrder.setStatus(UseOrderStatusEnum.ABNORMAL.getCode());
    useOrderMapper.updateById(useOrder);
    checkUseOrder(req);

    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(
                arch ->
                    arch.getStatus().equals(UseOrderStatusEnum.WAIT_RECEIVE.getCode())
                        || arch.getStatus().equals(UseOrderStatusEnum.PASSED.getCode())
                        || arch.getStatus().equals(UseOrderStatusEnum.LATE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "待领取，借阅中，逾期的文档才能进行终止利用操作");
    }
    useArches.forEach(
        arch -> {
          arch.setStatus(UseOrderStatusEnum.ABNORMAL.getCode());
          arch.setUpdateBy(SecurityUtil.getUserId());
          arch.setUpdateTime(new Date());
        });
    useArchSvc.updateBatchById(useArches);
    // 添加日志
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    req.setArchContent(archContent);
    req.setCreateBy(SecurityUtil.getUserId());
    req.setCreateUserName(SecurityUtil.getUsername());
    req.setReason(req.getReason());
    req.setUserName(SecurityUtil.getUsername());
    req.setUseDate(new Date());
    useLogSvc.asyncSaveLog(req);
    // 终止利用，添加缺损登记，不归还库存
    // todo...
  }

  /**
   * 遗失
   *
   * @param req
   */
  public void lost(UseOrderManageDTO req) {

    checkUseOrder(req);

    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(
                arch ->
                    arch.getStatus().equals(UseOrderStatusEnum.PASSED.getCode())
                        || arch.getStatus().equals(UseOrderStatusEnum.LATE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "逾期，已领取的文档才能进行遗失操作");
    }
    useArches.forEach(
        arch -> {
          arch.setStatus(UseOrderStatusEnum.ABNORMAL.getCode());
          arch.setUpdateBy(SecurityUtil.getUserId());
          arch.setUpdateTime(new Date());
          // 扣减电子文件数量
          if (arch.getTableId().contains(ArchTmplBO.DOCUMENT)) {
            Document qryDoc = new Document();
            qryDoc.setTableId(arch.getTableId());
            qryDoc.setId(arch.getArchId());
            Document doc = documentSvc.view(qryDoc);
            // 扣减归档份数
            Integer fillingNum = Optional.ofNullable(doc.getFillingNum()).orElse(0);
            int newStock = fillingNum - 1;
            doc.setFillingNum(Math.max(newStock, 0));
            documentSvc.updateById(doc);
          }

          // 扣减电子文件数量
          if (arch.getTableId().contains(ArchTmplBO.VOLUME)) {
            Volume qryVol = new Volume();
            qryVol.setTableId(arch.getTableId());
            qryVol.setId(arch.getArchId());
            Volume vol = volumeSvc.view(qryVol);
            Integer fillingNum = Optional.ofNullable(vol.getFillingNum()).orElse(0);
            int newStock = fillingNum - 1;

            vol.setFillingNum(Math.max(newStock, 0));
            volumeSvc.updateById(vol);
          }
        });
    useArchSvc.updateBatchById(useArches);
    // 添加日志
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    req.setArchContent(archContent);
    req.setCreateBy(SecurityUtil.getUserId());
    req.setCreateUserName(SecurityUtil.getUsername());
    useLogSvc.asyncSaveLog(req);
  }

  /**
   * 通知领取
   *
   * @param req
   */
  public void noticeToReceive(UseOrderManageDTO req) {

    UseOrderVO useOrderVO = checkUseOrder(req);

    if (useOrderVO.getUserType().equals(UseUserTypeEnum.OUTSIDE_THE_SYSTEM.getType())) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "系统内用户才能进行通知操作");
    }
    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(arch -> arch.getStatus().equals(UseOrderStatusEnum.WAIT_RECEIVE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "待领取的档案才能进行通知操作");
    }
    // 添加通知
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    SysMessagesAddDTO dto = new SysMessagesAddDTO();
    dto.setTitle(confI18nSvc.format(MessageConst.GET_NOTIFICATION_TITLE));
    //    StrUtil.format(
    //            "你借阅的【{}】,已经可以领取",
    //            archContent);
    dto.setMsgContent(
        confI18nSvc.format(
            MessageConst.GET_NOTIFICATION_CONTENT, I18nTypeEnum.PROMPT_MESSAGE, archContent));
    dto.setReceiver(String.valueOf(useOrderVO.getUserId()));
    dto.setSendType(MessageSendTypeEnum.USER.getType());
    dto.setMsgCategory(MessageTypeEnum.NOTICE.getType());
    dto.setComId(useOrderVO.getComId());
    messagesSvc.add(dto);
  }

  /**
   * 催还
   *
   * @param req
   */
  public void recall(UseOrderManageDTO req) {

    UseOrderVO useOrderVO = checkUseOrder(req);
    if (useOrderVO.getUserType().equals(UseUserTypeEnum.OUTSIDE_THE_SYSTEM.getType())) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "系统内用户才能进行通知操作");
    }
    List<UseArch> useArches = useArchSvc.listByIds(IdUtil.split(req.getUseArchIds()));
    List<UseArch> filterList =
        useArches.stream()
            .filter(arch -> arch.getStatus().equals(UseOrderStatusEnum.LATE.getCode()))
            .collect(Collectors.toList());
    if (ObjectUtil.notEqual(filterList, useArches)) {
      throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_INVALIDED, "已逾期的档案才能进行通知操作");
    }
    // 添加通知
    String archContent = useArches.stream().map(UseArch::getTitle).collect(Collectors.joining(","));
    SysMessagesAddDTO dto = new SysMessagesAddDTO();
    //    "借阅归还通知"
    dto.setTitle(confI18nSvc.format(MessageConst.RETURN_NOTIFICATION_TITLE));
    //    StrUtil.format("你借阅的【{}】,已经逾期，请及时归还", archContent)
    dto.setMsgContent(
        confI18nSvc.format(
            MessageConst.RETURN_NOTIFICATION_CONTENT, I18nTypeEnum.PROMPT_MESSAGE, archContent));
    dto.setReceiver(String.valueOf(useOrderVO.getUserId()));
    dto.setMsgCategory(MessageTypeEnum.NOTICE.getType());
    dto.setComId(useOrderVO.getComId());
    messagesSvc.add(dto);
  }

  @Override
  public UseOrderCodeVO useCode() {

    UseArch one =
        useArchSvc.getOne(
            Wrappers.<UseArch>lambdaQuery().last("limit 1").orderByDesc(UseArch::getUseSequence));
    int sequence =
        Optional.ofNullable(Optional.ofNullable(one).orElse(new UseArch()).getUseSequence())
            .orElse(0);
    return UseOrderCodeVO.builder()
        .useCode(
            StrUtil.format(
                "{}-{}-{}", DateUtil.year(new Date()), "WJ", IdUtil.autoGenCode(sequence, 3)))
        .sequence(sequence + 1)
        .build();
  }

  @Override
  public Long countApplyNum(Long fondsId, List<Integer> entTypeList) {
    return useOrderMapper.selectCount(
        Wrappers.<UseOrder>query()
            .lambda()
            .eq(UseOrder::getFondsId, fondsId)
            // 过滤
            .in(
                UseOrder::getStatus,
                UseOrderStatusEnum.PASSED.getCode(),
                UseOrderStatusEnum.FINISHED.getCode())
            // 利用类型过滤
            .in(ObjectUtil.isNotEmpty(entTypeList), UseOrder::getEntType, entTypeList));
  }

  /**
   * 以卷装订文件操作校验
   *
   * @param archiveTypeVO 门类
   * @param doc 文件
   */
  private void checkBindWithVol(ArchiveTypeVO archiveTypeVO, Document doc) {
    // 非组卷门类，直接返回
    if (ObjectUtil.isNull(archiveTypeVO.getHaveVol())
        || BooleanUtil.isFalse(archiveTypeVO.getHaveVol())) {
      return;
    }

    Long volId = doc.getVolId();
    // 未组卷文件，无法校验，直接返回
    if (ObjectUtil.isNull(volId)) {
      return;
    }
    // 查询文件所属案卷信息
    Volume qryVOl = new Volume();
    qryVOl.setTableId(archiveTypeVO.getVolTableId());
    qryVOl.setArchTypeId(archiveTypeVO.getId());
    qryVOl.setId(volId);
    Volume view = volumeSvc.view(qryVOl);
    if (ObjectUtil.isNull(view)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "未找到文件：" + doc.getTitle() + "所属案卷信息");
    }
    // 案卷以卷装订，直接提示无法操作
    boolean bindWithVol = bindWithVol(view.getBindStatus());
    if (BooleanUtil.isTrue(bindWithVol)) {
      throw BizTipException.instance(
          ErrorCode.OP_BIND_VOL_DOC, "文件：" + doc.getTitle() + " 所属案卷【以卷装订】，无法单独操作卷内文件");
    }
  }

  /**
   * 装订方式 以卷装订
   *
   * @param bindStatus
   * @return
   */
  private boolean bindWithVol(Integer bindStatus) {
    return ObjectUtil.isNull(bindStatus) || BindStatusEnum.VOL.getCode().equals(bindStatus);
  }

  /**
   * 审核通过
   *
   * @param id
   */
  @Transactional(rollbackFor = Exception.class)
  @Override
  public void agree(Long id) {
    UseOrder useOrder = useOrderMapper.selectById(id);
    if (ObjectUtil.isNull(useOrder)) {
      return;
    }
    DateTime expireDate =
        DateUtil.offsetDay(
            new Date(),
            Integer.parseInt(
                Optional.ofNullable(useOrder.getPeriod()).orElse(Constants.DEF_USE_DAY)));

    Integer statusEnum = null;
    if (ImmutableSet.of(
            UseOrderTypeEnum.LOAN_IN_KIND.getCode(),
            UseOrderTypeEnum.ON_SITE_BORROWING.getCode(),
            UseOrderTypeEnum.COPY_OUT.getCode())
        .contains(useOrder.getEntType())) {
      statusEnum = UseOrderStatusEnum.WAIT_RECEIVE.getCode();
    } else {
      statusEnum = UseOrderStatusEnum.PASSED.getCode();
    }
    useOrder.setStatus(statusEnum);
    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(useOrder.getId());
    List<UseArchListVO> archList = useArchSvc.selectList(qry);
    if (ObjectUtil.isNotEmpty(archList)) {
      Integer finalStatusEnum = statusEnum;
      List<UseArch> useArches =
          archList.stream()
              .map(
                  arch -> {
                    UseArch useArch = BeanUtil.copyProperties(arch, UseArch.class);
                    useArch.setUseSequence(UseOrderStatusEnum.PASSED.getCode());
                    useArch.setStatus(finalStatusEnum);
                    if (UseOrderStatusEnum.PASSED.getCode().equals(finalStatusEnum)) {
                      useArch.setEndTime(expireDate);
                    }
                    return useArch;
                  })
              .collect(Collectors.toList());
      useArchSvc.updateBatchById(useArches);
    }
    if (UseOrderStatusEnum.PASSED.getCode().equals(statusEnum)) {
      useOrder.setExpireTime(expireDate);
    }
    useOrderMapper.updateById(useOrder);
  }

  @Override
  public void audit(UseOrderAuditDTO req) {
    UseOrder useOrder = useOrderMapper.selectById(req.getId());
    if (ObjectUtil.isNull(useOrder)) {
      return;
    }
    DateTime expireDate =
        DateUtil.offsetDay(
            new Date(),
            Integer.parseInt(
                Optional.ofNullable(useOrder.getPeriod()).orElse(Constants.DEF_USE_DAY)));

    Integer statusEnum = UseOrderStatusEnum.getValue(req.getStatus()).getCode();
    if (ImmutableSet.of(
                UseOrderTypeEnum.LOAN_IN_KIND.getCode(),
                UseOrderTypeEnum.ON_SITE_BORROWING.getCode(),
                UseOrderTypeEnum.COPY_OUT.getCode())
            .contains(useOrder.getEntType())
        && ObjectUtil.equals(statusEnum, UseOrderStatusEnum.PASSED.getCode())) {
      statusEnum = UseOrderStatusEnum.WAIT_RECEIVE.getCode();
    }
    useOrder.setStatus(statusEnum);
    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(useOrder.getId());
    List<UseArchListVO> archList = useArchSvc.selectList(qry);
    if (ObjectUtil.isNotEmpty(archList)) {
      Integer finalStatusEnum = statusEnum;
      List<UseArch> useArches =
          archList.stream()
              .map(
                  arch -> {
                    UseArch useArch = BeanUtil.copyProperties(arch, UseArch.class);
                    useArch.setUseSequence(UseOrderStatusEnum.FINISHED.getCode());
                    useArch.setStatus(finalStatusEnum);
                    if (UseOrderStatusEnum.FINISHED.getCode().equals(finalStatusEnum)) {
                      useArch.setEndTime(expireDate);
                    }
                    return useArch;
                  })
              .collect(Collectors.toList());
      useArchSvc.updateBatchById(useArches);
    }
    if (UseOrderStatusEnum.FINISHED.getCode().equals(statusEnum)) {
      useOrder.setExpireTime(expireDate);
    }
    useOrder.setOpTime(new Date());
    useOrderMapper.updateById(useOrder);
  }

  @Override
  public void evalEdit(StatisticsAddDTO req) {
    // 审核通过，添加报表统计记录
    // 获取用户当天数据，对当前数据进行累加，保存数据，修改利用申请状态
    if (ObjectUtil.isEmpty(req.getOrderId())) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "必传参数[id]为空！");
    }
    UseOrder useOrder = useOrderMapper.selectById(req.getOrderId());
    StatisticsVO entity = statisticsService.view(req.getUserId());

    entity.setUserId(useOrder.getUserId());
    SysUserVO sysUser = sysUserSvc.view(entity.getUserId());
    // 次
    entity.setMainOrder(1L);
    // 人次
    entity.setPersonOrder(
        ObjUtil.isEmpty(sysUser.getVisitorNumber()) ? 1L : sysUser.getVisitorNumber());

    // 卷次
    entity.setVolOrder(add(entity.getVolOrder(), req.getVolOrder()));
    // 件次
    entity.setDocOrder(add(entity.getDocOrder(), req.getDocOrder()));
    // 建国前,建国后
    entity.setJgq(add(entity.getJgq(), req.getJgq()));
    entity.setJgh(add(entity.getJgh(), req.getJgh()));

    // 档案载体(纸质)
    entity.setCarrierP(add(entity.getCarrierP(), req.getCarrierP()));
    // 档案载体(数字化)
    entity.setCarrierD(add(entity.getCarrierD(), req.getCarrierD()));
    // 档案载体(声像)
    entity.setCarrierS(add(entity.getCarrierS(), req.getCarrierS()));

    entity.setKfq(add(entity.getKfq(), req.getKfq()));
    entity.setWkf(add(entity.getWkf(), req.getWkf()));

    // 设置利用申请时提交的数据
    /** 申请类型,1:摘抄复制,2:静电复印,3:拍摄复制,4:电子打印,5:电子拷贝 */
    entity.setZcfs(add(entity.getZcfs(), req.getZcfs()));
    entity.setZcys(add(entity.getZcys(), req.getZcys()));
    entity.setJdfyfs(add(entity.getJdfyfs(), req.getJdfyfs()));
    entity.setJdfyys(add(entity.getJdfyys(), req.getJdfyys()));
    entity.setSyfzfs(add(entity.getSyfzfs(), req.getSyfzfs()));
    entity.setSyfzys(add(entity.getSyfzys(), req.getSyfzys()));
    entity.setSzhdyfs(add(entity.getSzhdyfs(), req.getSzhdyfs()));
    entity.setSzhdyys(add(entity.getSzhdyys(), req.getSzhdyys()));
    entity.setSzhkbfs(add(entity.getSzhkbfs(), req.getSzhkbfs()));
    entity.setSzhkbys(add(entity.getSzhkbys(), req.getSzhkbys()));

    StatisticsAddDTO statisticsAdd = BeanUtil.copyProperties(entity, StatisticsAddDTO.class);
    statisticsService.add(statisticsAdd);
    useOrder.setEvalStatus(1);
    useOrderMapper.updateById(useOrder);
  }

  @Override
  public StatisticsVO viewEvaluate(Long id) {
    if (ObjectUtil.isEmpty(id)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "必传参数[id]为空！");
    }
    StatisticsVO entity = new StatisticsVO();
    UseOrder useOrder = useOrderMapper.selectById(id);
    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(id);
    List<UseArchListVO> useArchList = useArchSvc.selectList(qry);
    UseArchListVO archListVO = useArchList.stream().findFirst().orElse(null);
    if (ObjectUtil.isEmpty(archListVO)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "未找到档案信息！");
    }

    String idsStr =
        useArchList.stream().map(i -> i.getArchId().toString()).collect(Collectors.joining(","));

    BaseBO baseBO = new BaseBO();
    baseBO.setArchTypeId(archListVO.getArchTypeId());
    baseBO.setTableId(archListVO.getTableId());
    baseBO.setDocIds(idsStr);
    List<Document> archList = documentSvc.selectListByIds(baseBO);

    entity.setUserId(useOrder.getUserId());
    SysUserVO sysUser = sysUserSvc.view(entity.getUserId());
    entity.setApplyDate(new Date());
    // 次
    entity.setMainOrder(1L);
    // 人次
    entity.setPersonOrder(
        ObjUtil.isEmpty(sysUser.getVisitorNumber()) ? 1L : sysUser.getVisitorNumber());

    List<Document> docList =
        archList.stream()
            .filter(x -> !DynamicTableNameUtil.checkTableVol(x.getTableId()))
            .collect(Collectors.toList());
    List<Document> volList =
        archList.stream()
            .filter(x -> DynamicTableNameUtil.checkTableVol(x.getTableId()))
            .collect(Collectors.toList());
    // 卷次
    entity.setVolOrder(entity.getVolOrder() + volList.size());
    // 件次
    entity.setDocOrder(entity.getDocOrder() + docList.size());
    // 建国前,建国后
    List<Document> jgqDocList =
        archList.stream()
            .filter(x -> ObjUtil.isNotEmpty(x.getYear()) && x.getYear() < 1949)
            .collect(Collectors.toList());
    List<Document> jghDocList =
        archList.stream()
            .filter(x -> ObjUtil.isNotEmpty(x.getYear()) && x.getYear() >= 1949)
            .collect(Collectors.toList());
    entity.setJgq(entity.getJgq() + jgqDocList.size());
    entity.setJgh(entity.getJgh() + jghDocList.size());

    // 档案载体(纸质)
    List<Document> carrierP =
        archList.stream()
            .filter(
                x ->
                    ObjUtil.equal("纸质", x.getPhysicalCategory())
                        || ObjUtil.equal("1", x.getPhysicalCategory()))
            .collect(Collectors.toList());
    entity.setCarrierP(entity.getCarrierP() + carrierP.size());

    // 档案载体(数字化)
    List<Document> carrierD =
        archList.stream()
            .filter(x -> ObjUtil.equal("是", x.getDigital()) || ObjUtil.equal("1", x.getDigital()))
            .collect(Collectors.toList());
    entity.setCarrierD(entity.getCarrierD() + carrierD.size());

    // 档案载体(声像)
    List<Document> carrierS =
        archList.stream()
            .filter(
                x ->
                    ObjUtil.notEqual("纸质", x.getPhysicalCategory())
                        && ObjUtil.notEqual("1", x.getPhysicalCategory()))
            .collect(Collectors.toList());
    entity.setCarrierS(entity.getCarrierS() + carrierS.size());

    List<Document> kfq =
        archList.stream()
            .filter(x -> ObjUtil.notEqual(ArchControlConst.CTL_2, x.getControlIdentifier()))
            .collect(Collectors.toList());
    List<Document> wkf =
        archList.stream()
            .filter(x -> !ObjUtil.notEqual(ArchControlConst.CTL_2, x.getControlIdentifier()))
            .collect(Collectors.toList());

    entity.setKfq(entity.getKfq() + kfq.size());
    entity.setWkf(entity.getWkf() + wkf.size());

    // 设置利用申请时提交的数据
    /** 申请类型,1:摘抄复制,2:静电复印,3:拍摄复制,4:电子打印,5:电子拷贝 */
    if (useOrder.getApplyType() == 1) {
      entity.setZcfs(ObjUtil.isEmpty(useOrder.getApplyWeight()) ? 0L : useOrder.getApplyWeight());
      entity.setZcys(ObjUtil.isEmpty(useOrder.getApplyPages()) ? 0L : useOrder.getApplyPages());
    }
    if (useOrder.getApplyType() == 2) {
      entity.setJdfyfs(ObjUtil.isEmpty(useOrder.getApplyWeight()) ? 0L : useOrder.getApplyWeight());
      entity.setJdfyys(ObjUtil.isEmpty(useOrder.getApplyPages()) ? 0L : useOrder.getApplyPages());
    }
    if (useOrder.getApplyType() == 3) {
      entity.setSyfzfs(ObjUtil.isEmpty(useOrder.getApplyWeight()) ? 0L : useOrder.getApplyWeight());
      entity.setSyfzys(ObjUtil.isEmpty(useOrder.getApplyPages()) ? 0L : useOrder.getApplyPages());
    }
    if (useOrder.getApplyType() == 4) {
      entity.setSzhdyfs(
          ObjUtil.isEmpty(useOrder.getApplyWeight()) ? 0L : useOrder.getApplyWeight());
      entity.setSzhdyys(ObjUtil.isEmpty(useOrder.getApplyPages()) ? 0L : useOrder.getApplyPages());
    }
    if (useOrder.getApplyType() == 5) {
      entity.setSzhkbfs(
          ObjUtil.isEmpty(useOrder.getApplyWeight()) ? 0L : useOrder.getApplyWeight());
      entity.setSzhkbys(ObjUtil.isEmpty(useOrder.getApplyPages()) ? 0L : useOrder.getApplyPages());
    }

    return entity;
  }

  public static long add(Long l1, Long l2) {
    l1 = ObjUtil.defaultIfNull(l1, 0L);
    l2 = ObjUtil.defaultIfNull(l2, 0L);
    return l1 + l2;
  }

  public static int add(Integer l1, Integer l2) {
    l1 = ObjUtil.defaultIfNull(l1, 0);
    l2 = ObjUtil.defaultIfNull(l2, 0);
    return l1 + l2;
  }

  /**
   * 跨单位利用申请审核通过
   *
   * @param id
   */
  @Override
  public void usingApplyAgree(Long id) {
    UseOrder useOrder = useOrderMapper.selectById(id);
    if (ObjectUtil.isNull(useOrder)) {
      return;
    }

    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(useOrder.getId());
    List<UseArchListVO> archList = useArchSvc.selectList(qry);
    if (ObjectUtil.isEmpty(archList)) {
      return;
    }
    // 利用申请，发起流程
  }

  /**
   * 审核拒绝
   *
   * @param id
   */
  @Transactional(rollbackFor = Exception.class)
  @Override
  public void reject(Long id) {
    UseOrder useOrder = useOrderMapper.selectById(id);
    useOrder.setStatus(UseOrderStatusEnum.REFUSE.getCode());

    if (ObjectUtil.isNull(useOrder)) {
      return;
    }
    UseArchQueryDTO qry = new UseArchQueryDTO();
    qry.setOrderId(useOrder.getId());
    List<UseArchListVO> archList = useArchSvc.selectList(qry);
    if (ObjectUtil.isNotEmpty(archList)) {
      List<UseArch> useArches =
          archList.stream()
              .map(
                  arch -> {
                    UseArch useArch = BeanUtil.copyProperties(arch, UseArch.class);
                    useArch.setStatus(UseOrderStatusEnum.REFUSE.getCode());
                    return useArch;
                  })
              .collect(Collectors.toList());
      useArchSvc.updateBatchById(useArches);
      if (ImmutableSet.of(
              UseOrderTypeEnum.LOAN_IN_KIND.getCode(), UseOrderTypeEnum.ON_SITE_BORROWING.getCode())
          .contains(useOrder.getEntType())) {
        // 归还预占库存
        checkOutArchStorage(useArches, true);
      }
    }
    useOrderMapper.updateById(useOrder);
  }
}
